#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <errno.h>
#include <string.h>

#include "IoOps.h"
/*
  std::string host;
  int port;
  int fd;
  iostat status;
*/

IoOps::IoOps(xmppLogger *logger)
{
  this->host = "";
  this->port = 0;
  this->fd = -1;
  this->status = 0;
  this->log = logger;
  this->fd = socket( PF_INET, SOCK_STREAM, 0 );
  if( 0 > this->fd ) {
    BIT_SET( this->status, XP_IOPS_ERROR );
    return;
  }
  if( 0 > socketpair( AF_UNIX, SOCK_STREAM,
                      0, this->alert_fds) ) {
    BIT_SET( this->status, XP_IOPS_ERROR );
    return;
  }
  this->nonblocking(this->fd,1);
  this->nonblocking(this->alert_fds[0],1);
  this->nonblocking(this->alert_fds[1],1);
}

IoOps::IoOps(const IoOps &rhs):
          host(rhs.host),port(rhs.port),
          fd(rhs.fd),status(rhs.status),
          log(rhs.log)
{
}

xret
IoOps::connect(const std::string &host, const int port)
{
  struct hostent *hp;
  struct sockaddr_in addr;
  int ret = -1;
  
  memset(&hp,0, sizeof(hp) );
  memset( &addr,0, sizeof(addr) );
  
  if( this->log )
    this->log->debug("Connection to host [%s] at port [%d]",
                      host.c_str(), port);

  if( xp_OK != sock_pton(host.c_str() , &addr.sin_addr) ) {/*it's not a IP address*/
    if( xp_OK != gethostinfo( host.c_str(), &hp) ) {
      if( this->log )
        this->log->fatal("Un-able to established connection [%s:%d]",
                      host.c_str(), port);
      return xp_FAIL;
    }
    addr.sin_addr.s_addr = ((struct in_addr*)(hp->h_addr))->s_addr;
  }
  addr.sin_family = PF_INET;
  addr.sin_port = htons( port );
  do {
    ret = ::connect( this->fd , (struct sockaddr*) &addr, sizeof(addr) );
  } while( ret == -1 && errno == EINTR );

  if( !ret ) {
    app_debug("%s", "We are connected now");
    if( this->log )
      this->log->debug("Connection established [%s:%d]",
                        host.c_str(), port);
    BIT_SET( this->status, XP_IOPS_CONNECTED );
    return xp_OK;
  }

  if( errno != EINPROGRESS ) {
    BIT_SET( this->status, XP_IOPS_ERROR );
    return xp_FAIL;
  }

  do {
    ioevent ev = 0;
    int l_sock_optval=-1;
    int l_sock_optval_len=sizeof(l_sock_optval);

    BIT_SET( this->status, XP_IOPS_CONNECTING );
    if( xp_OK != this->watch((ioevent)XP_IOPS_WRITE, ev, -1) )
      return xp_FAIL;
    if( ev & XP_IOPS_ERROR )
      return xp_FAIL;

    if(getsockopt( this->fd, SOL_SOCKET, SO_ERROR,
                  (int*)&l_sock_optval,(socklen_t*)&l_sock_optval_len) !=0) {
      BIT_UNSET( this->status, XP_IOPS_CONNECTING );
      BIT_SET( this->status, XP_IOPS_ERROR );
      if( this->log )
        this->log->fatal("Un-able to established connection [%s:%d]",
                      host.c_str(), port);
      return xp_FAIL;
    }
    if(l_sock_optval == 0) {
      BIT_SET( this->status, XP_IOPS_CONNECTED );
      BIT_UNSET( this->status, XP_IOPS_CONNECTING );
      if( this->log )
        this->log->debug("Connection established [%s:%d]",
                        host.c_str(), port);
      return xp_OK;
    } else {
      BIT_UNSET( this->status, XP_IOPS_CONNECTING );
      BIT_SET( this->status, XP_IOPS_ERROR );
      if( this->log )
        this->log->fatal("Un-able to established connection [%s:%d]",
                      host.c_str(), port);
      return xp_FAIL;
    }
  } while(0);
  BIT_UNSET( this->status, XP_IOPS_CONNECTING );
  BIT_SET( this->status, XP_IOPS_ERROR );
  if( this->log )
    this->log->fatal("Un-able to established connection [%s:%d]",
                      host.c_str(), port);
  return xp_FAIL;
}

xret
IoOps::watch(const ioevent watch_ev, ioevent &trigg_ev, short timeout)
{
  struct pollfd pfd[2];
  int ret = -1;
  int block_wait = -1;
  

  if( this->status & XP_IOPS_ERROR || this->status & XP_IOPS_EOF ) {
    BIT_SET( trigg_ev, XP_IOPS_EOF );
    BIT_SET( trigg_ev, XP_IOPS_ERROR );
    return xp_FAIL;
  }

  if( 0 < timeout )
    block_wait = timeout * 1000;

  if( this->log )
    this->log->debug("Timeout in sec [%d]", block_wait );

  do {
    memset( pfd, 0, sizeof(struct pollfd)*2 );

    pfd[0].fd = this->fd;
    if( watch_ev & XP_IOPS_READ )
      pfd[0].events |= POLLIN;
    if( watch_ev & XP_IOPS_WRITE )
      pfd[0].events |= POLLOUT;

    pfd[1].fd = this->alert_fds[0];
    pfd[1].events |= POLLIN;

    ret = poll( pfd, 2 , block_wait);
    if( 0 < ret ) {
      if( pfd[0].revents & POLLIN ) {
        BIT_SET(trigg_ev, XP_IOPS_READ);
        if( this->log )
          this->log->debug("Read activity");
      }
      if( pfd[1].revents & POLLIN ) {
        BIT_SET(trigg_ev, XP_IOPS_ALERT);
        if( this->log )
          this->log->debug("Alert activity");
      }

      if( pfd[0].revents & POLLOUT ) {
        BIT_SET( trigg_ev, XP_IOPS_WRITE );
        if( this->log )
          this->log->debug("Write activity");
      }
      if( pfd[0].revents & POLLHUP ) {
        BIT_SET( trigg_ev, XP_IOPS_EOF );
        if( this->log )
          this->log->debug("CLOSE activity");
      }
      if( pfd[0].revents & POLLERR ||
          pfd[0].revents & POLLNVAL ) {
        BIT_SET( trigg_ev, XP_IOPS_ERROR );
        if( this->log )
          this->log->error("ERROR activity");
      }
      return xp_OK;
    } else if( 0 == ret ) {
      if( this->log )
        this->log->debug("TIMEOUT after in activity of [%d]", block_wait);
      return xp_TIMEOUT;
    }
  } while( errno == EINTR && 0 > ret );
  BIT_SET( trigg_ev, XP_IOPS_ERROR );
  if( this->log )
    this->log->error("%s" , "NO defined activity occured."
                            "This should have never happened.");
  return xp_FAIL;
}

xret
IoOps::getStatus()
{
  if( this->status & XP_IOPS_EOF )
    return xp_EOF;
  else if( this->status & XP_IOPS_ERROR )
    return xp_FAIL;
  else if( this->status & XP_IOPS_CONNECTING )
    return xp_CONNECTING;
  else if( this->status & XP_IOPS_CONNECTED )
    return xp_CONNECTED;
  else
    return xp_OK;
}

#if 0
/*we shall be using iksemel provided IO facility*/
xret
IoOps::read(char *buf, const ssize_t len, ssize_t &read_len)
{
  char *s = (char *)buf;
  ssize_t res, pos = 0;

  if( this->status & XP_IOPS_ERROR || this->status & XP_IOPS_EOF ||
      (this->status & XP_IOPS_CONNECTING &&
       !(this->status & XP_IOPS_CONNECTED)) )
    return xp_FAIL;

  while( len > pos) {
    res = recv(this->fd, s+pos, len - pos, MSG_DONTWAIT);
    if( 0 > res ) {
      if( errno == EINTR )
        continue;
      else if( errno == EAGAIN ) {
        read_len = pos;
        return xp_EAGAIN;
      }
      else {
        BIT_SET( this->status, XP_IOPS_ERROR);
        return xp_FAIL;
      }
    }
    if( 0 == res ) {
      BIT_SET(this->status, XP_IOPS_EOF);
      read_len = pos;
      return xp_EOF;
    }
    pos += res;
  }
  read_len = pos;
  return xp_OK;
}

xret
IoOps::write(char *buf, ssize_t len, ssize_t &write_len)
{
  char *s = (char *)buf;
  ssize_t res, pos = 0;

  if( this->status & XP_IOPS_ERROR ||
      (this->status & XP_IOPS_CONNECTING &&
       !(this->status & XP_IOPS_CONNECTED)) )
    return xp_FAIL;

  while( len > pos ) {
    res = send(this->fd, s+pos, len - pos, 0);
    if( 0 > res ) {
      if( errno == EINTR )
        continue;
      else if(errno == EAGAIN ) {
        write_len = pos;
        return xp_EAGAIN;
      } else {
        BIT_SET( this->status, XP_IOPS_ERROR);
        return xp_FAIL;
      }
    }
    pos += res;
  }
  write_len = pos;
  return xp_OK;
}
#endif
xret
IoOps::nonblocking(int& socket, unsigned short enable)
{
  int re = 0;
  int flags = 0;

  flags = fcntl( socket , F_GETFL, 0);
  if(flags < 0)
    return xp_ESYS;

  if(enable)
    BIT_SET(flags, O_NONBLOCK);
  else
    BIT_UNSET(flags, O_NONBLOCK);

  re = fcntl( socket, F_SETFL, flags);
  if(re < 0)
    return xp_ESYS;
  return xp_OK;
}

xret
IoOps::sock_pton(const char *host, struct in_addr *inp)
{
  int re = 0;
  re = inet_aton(host, inp);
  if (re == 0) return xp_FAIL;
  return xp_OK;
}

xret
IoOps::gethostinfo(const char *host,struct hostent **hp)
{
  #define GETHOSTBYNAME_BUF_LEN 512
  int r = 0;
  struct hostent hs;
  int h_errnop = 0;
  char buf[GETHOSTBYNAME_BUF_LEN];
  extern int h_errno;

  r = gethostbyname_r(host,
              &hs, buf, GETHOSTBYNAME_BUF_LEN - 1,
              hp, &h_errnop);

  if(r != 0) {
    if (h_errnop == TRY_AGAIN) {
      return xp_EAGAIN;
    }
    return xp_FAIL;
  }
  return xp_OK;
}

xret
IoOps::close()
{
  ::close(this->fd);
  this->status |= XP_IOPS_EOF;
  return xp_OK;
}

/*
vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=2:encoding=utf-8:textwidth=80
*/
